#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <cstdlib>

#include "HttpResponse.h"
#include "FastCGI.h"

using namespace std;

const uint32 BUF_SIZE = 1024*64;//64K

FastCGI::FastCGI()
{
    _header   = new FCGI_Header;
    _response = new HttpResponse;
    _buf      = CreateCStr(BUF_SIZE);
    _paramsLen = 0;
    _requestID = 0;
    _FastCGIfd = -1;
}

FastCGI::~FastCGI()
{
    if (_FastCGIfd >= 0)
    {
        close(_FastCGIfd);
    }

    delete _header;
    delete _response;
    delete [] _buf;
}

int FastCGI::GetFastCGIFd()
{
    return _FastCGIfd;
}
int FastCGI::Process(const int sockfd, const HttpRequest* req)
{
    if (BeginRequest() < 0)
    {
        return -1;
    }

    if (SendParam() < 0)
    {
        return -1;
    }

    if (SendPost(req) < 0)
    {
        return -1;
    }

    if (Response(sockfd, req) < 0)
    {
        return -1;
    }

    return 0;
}

int FastCGI::BeginRequest()
{
    FCGI_BeginRequestBody requestBody;
    memset(&requestBody, 0, sizeof(FCGI_BeginRequestBody));
    requestBody.roleB0 = FCGI_RESPONDER;

    return ((SendHeader(FCGI_BEGIN_REQUEST, sizeof(FCGI_BeginRequestBody), 0) >= 0)
        && (writen(_FastCGIfd, (char *)&requestBody, sizeof(FCGI_BeginRequestBody)) >= 0))
        ? 0 : -1;
}

int FastCGI::SendParam()
{
    if (_paramsLen > 0
        && SendHeader(FCGI_PARAMS, _paramsLen, 0) >= 0
        && writen(_FastCGIfd, _buf, _paramsLen) >= 0
        && SendHeader(FCGI_PARAMS, 0, 0) >= 0)
    {
        memset(_buf, 0, _paramsLen);
        _paramsLen = 0;

        return 0;
    }

    return -1;
}

int FastCGI::SendPost(const HttpRequest* req)
{
    if (req->GetPostDataLen() == 0)
    {
        return 0;
    }

    if (SendHeader(FCGI_STDIN, req->GetPostDataLen(), 0) >= 0
        && req->GetPostDataLen() >= 0
        && writen(_FastCGIfd, req->GetPostData(), req->GetPostDataLen()) >= 0
        && SendHeader(FCGI_STDIN, 0, 0) >= 0)
    {
        return 0;
    }

    return -1;
}

int FastCGI::Response(const int sockfd, const HttpRequest* req)
{
    bool headerFlg = true;
    _response->SetHttpVersion(req->GetVersion());
    _response->SetStatusAndReason("200", "OK");

    while (true)
    {
        memset(_buf, 0, BUF_SIZE);
        memset(_header, 0, sizeof(FCGI_Header));

        if (readn(_FastCGIfd, (char *)_header, sizeof(FCGI_Header)) < 0)
        {
            return -1;
        }

        if (_header->type != FCGI_STDOUT && _header->type != FCGI_STDERR)
        {
            break;
        }

        int bodyLen = ((int)_header->contentLengthB1) << 8 | _header->contentLengthB0;
        int paddingLen = _header->paddingLength;
        int totalLen = bodyLen + paddingLen;

        if (totalLen <= 0)
        {
            continue;
        }

        if (readn(_FastCGIfd, _buf, totalLen) < 0)
        {
            return -1;
        }

        if (bodyLen <= 0)
        {
            continue;
        }

        switch (_header->type)
        {
            case FCGI_STDOUT:
                if (headerFlg)
                {
                    SetStatus(_response);
                    string mainHead = _response->GetResponseStatusLine();
                    if (writen(sockfd, mainHead.c_str(), mainHead.size()) < 0)
                    {
                        return -1;
                    }
                    headerFlg = false;
                }

                if (writen(sockfd, _buf, bodyLen) < 0)
                {
                    return -1;
                }
                break;

            case FCGI_STDERR:
                if (headerFlg)
                {
                    _response->SetStatusAndReason("500", "Internal Server Error");
                }
                //DOTO:will log PHP error msg in FastServer log later, but print the msg now
                cout<<_buf<<endl;
                break;
        }
    }

    return _header->type == FCGI_END_REQUEST ? 0 : -1;
}

void FastCGI::SetStatus(HttpResponse* response)
{
    if(response->GetStatusCode() == "500")
    {
        return;
    }

    const char* t_data = strstr(_buf, CRLF);

    if (t_data == NULL)
    {
        return;
    }

    ulong len = (ulong)t_data - (ulong)_buf;
    char* status = CreateCStr(len + 1);
    strncpy(status, _buf, len);
    string statusStr(status);
    delete [] status;

    int32 index = statusStr.find(SPLIT);
    if (index < 0)
    {
        return;
    }

    if (statusStr.substr(0, index) == "Status")
    {
        response->SetStatusAndReason(statusStr.substr(index+2, index+5), statusStr.substr(index+5, statusStr.size()));
    }
}

int FastCGI::ConnectFastCGI(const char *IP, const uint32 port)
{
    if ((_FastCGIfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        return -1;
    }

    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(sockaddr_in));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port);

    return inet_pton(AF_INET, IP, &servaddr.sin_addr) > 0
        && connect(_FastCGIfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) >= 0
        ? _FastCGIfd : -1;
}

void FastCGI::SetRequestID(const int requestID)
{
    _requestID = requestID;
}

int FastCGI::SendHeader(uchar type, int contentLength, int paddingLength)
{
    if (_requestID <= 0)
    {
        return -1;
    }

    _header->version = FCGI_VERSION;
    _header->type = type;
    _header->requestIdB1 = (uchar)(_requestID>>8);
    _header->requestIdB0 = (uchar)_requestID;
    _header->contentLengthB1 = (uchar)(contentLength>>8);
    _header->contentLengthB0 = (uchar)contentLength;
    _header->paddingLength = (uchar)paddingLength;
    _header->reserved = 0;

    return writen(_FastCGIfd, (char *)_header, sizeof(FCGI_Header)) < 0
        ? -1 : 0;
}

void FastCGI::AddParam(const char *name, const char *value)
{
    int nameLen = strlen(name);
    int valueLen = strlen(value);

    if (nameLen >= 0x80)
    {
        _buf[_paramsLen++] = (nameLen>>24)|0x80;
        _buf[_paramsLen++] = nameLen>>16;
        _buf[_paramsLen++] = nameLen>>8;
        _buf[_paramsLen++] = nameLen;
    }
    else
    {
        _buf[_paramsLen++] = nameLen;
    }

    if (valueLen >= 0x80)
    {
        _buf[_paramsLen++] = (valueLen>>24)|0x80;
        _buf[_paramsLen++] = valueLen>>16;
        _buf[_paramsLen++] = valueLen>>8;
        _buf[_paramsLen++] = valueLen;
    }
    else
    {
        _buf[_paramsLen++] = valueLen;
    }

    memcpy( &_buf[_paramsLen], name, nameLen);
    _paramsLen += nameLen;
    memcpy( &_buf[_paramsLen], value, valueLen);
    _paramsLen += valueLen;
}
